from ctypes import byref, c_buffer, c_float, c_void_p, c_uint, c_short, c_uint, create_string_buffer, string_at, CDLL, sizeof
import os
selfDir = os.path.dirname(__file__)
SETTING_USE_ANTI_ALIAS_FILTER = 0 # Enable/disable anti-alias filter in pitch transposer (0 = disable)
SETTING_ANTI_ALIAS_FILTER_LENGTH = 1 # Pitch transposer anti-alias filter length (8 .. 128 taps, default = 32)
SETTING_USE_QUICK_SEEK = 2 # Enable/disable quick seeking algorithm in tempo changer routine (enabling quick seeking lowers CPU utilization but causes a minor sound quality compromising)
SETTING_SEQUENCE_MILLISECONDS = 3 # Time-stretch algorithm single processing sequence length in milliseconds. This determines to how long sequences the original sound is chopped in the time-stretch algorithm. See "STTypes.h" or README for more information.
SETTING_SEEK_WINDOW_MILLISECONDS = 4 # Time-stretch algorithm seeking window length in milliseconds for algorithm that finds the best possible overlapping location. This determines from how wide window the algorithm may look for an optimal joining location when mixing the sound sequences back together. See "STTypes.h" or README for more information.
SETTING_OVERLAP_MILLISECONDS = 5 # Time-stretch algorithm overlap length in milliseconds. When the chopped sound sequences are mixed back together, to form a continuous sound stream, this parameter defines over how long period the two consecutive sequences are let to overlap each other. See "STTypes.h" or README for more information.
'''
 Call "getSetting" with this ID to query processing sequence size in samples. This value gives approximate value of how many input samples you'll need to feed into SoundTouch after initial buffering to get out a new batch of output samples.
 This value does not include initial buffering at beginning of a new processing stream, use SETTING_INITIAL_LATENCY to get the initial buffering size. Notices:
 - This is read-only parameter, i.e. setSetting ignores this parameter
 - This parameter value is not constant but change depending on tempo/pitch/rate/samplerate settings.
 '''
SETTING_NOMINAL_INPUT_SEQUENCE = 6
'''
 Call "getSetting" with this ID to query nominal average processing output size in samples. This value tells approcimate value how many output samples SoundTouch outputs once it does DSP processing run for a batch of input samples.
 Notices: 
 - This is read-only parameter, i.e. setSetting ignores this parameter
 - This parameter value is not constant but change depending on tempo/pitch/rate/samplerate settings.
 '''
SETTING_NOMINAL_OUTPUT_SEQUENCE = 7
'''
 Call "getSetting" with this ID to query initial processing latency, i.e. approx. how many samples you'll need to enter to SoundTouch pipeline before you can expect to get first batch of ready output samples out. 
 After the first output batch, you can then expect to get approx. SETTING_NOMINAL_OUTPUT_SEQUENCE ready samples out for every SETTING_NOMINAL_INPUT_SEQUENCE samples that you enter into SoundTouch. Example:
 processing with parameter -tempo=5 => initial latency = 5509 samples input sequence  = 4167 samples output sequence = 3969 samples
 Accordingly, you can expect to feed in approx. 5509 samples at beginning of the stream, and then you'll get out the first 3969 samples. After that, for every approx. 4167 samples that you'll put in, you'll receive again approx. 3969 samples out.
 This also means that average latency during stream processing is INITIAL_LATENCY-OUTPUT_SEQUENCE/2, in the above example case 5509-3969/2 = 3524 samples
 Notices: 
 - This is read-only parameter, i.e. setSetting ignores this parameter
 - This parameter value is not constant but change depending on tempo/pitch/rate/samplerate settings.
 '''
SETTING_INITIAL_LATENCY = 8
dll = CDLL(os.path.join(selfDir, "bin", "SoundTouch.dll"))
def getVersionId():
    return dll.soundtouch_getVersionId()
def getVersionString():
    r = dll.soundtouch_getVersionString()
    return string_at(r).decode()
class SoundTouch:
    def __init__(self):
        self.handle = dll.soundtouch_createInstance()
    def __del__(self):
        if hasattr(self, 'handle'):
            dll.soundtouch_destroyInstance(self.handle)
    def setRate(self, rate):
        dll.soundtouch_setRate.argtypes = (c_void_p, c_float,)
        dll.soundtouch_setRate(self.handle, rate)
    def setTempo(self, tempo):
        # Sets new tempo control value. Value: Tempo setting. Normal tempo = 1.0, smaller values represent slower tempo, larger faster tempo.
        dll.soundtouch_setTempo.argtypes = (c_void_p, c_float,)
        dll.soundtouch_setTempo(self.handle, tempo)
    def setRateChange(self, rate):
        # Sets new rate control value as a difference in percents compared to the original rate (-50 .. +100 %); Value: Rate setting is in %
        dll.soundtouch_setRateChange.argtypes = (c_void_p, c_float,)
        dll.soundtouch_setRateChange(self.handle, rate)
    def setTempoChange(self, tempo):
        # Sets new tempo control value as a difference in percents compared to the original tempo (-50 .. +100 %);
        dll.soundtouch_setTempoChange.argtypes = (c_void_p, c_float,)
        dll.soundtouch_setTempoChange(self.handle, tempo)
    def setPitch(self, pitch):
        # Sets new pitch control value. Value: Pitch setting. Original pitch = 1.0, smaller values represent lower pitches, larger values higher pitch.
        dll.soundtouch_setPitch.argtypes = (c_void_p, c_float,)
        dll.soundtouch_setPitch(self.handle, pitch)
    def setPitchOctaves(self, pitch):
        # Sets pitch change in octaves compared to the original pitch (-1.00 .. +1.00 for +- one octave); Value: Pitch setting in octaves
        dll.soundtouch_setPitchOctaves.argtypes = (c_void_p, c_float,)
        dll.soundtouch_setPitchOctaves(self.handle, pitch)
    def setPitchSemiTones(self, pitch):
        # Sets pitch change in semi-tones compared to the original pitch (-12 .. +12 for +- one octave); Value: Pitch setting in semitones
        dll.soundtouch_setPitchSemiTones.argtypes = (c_void_p, c_float,)
        dll.soundtouch_setPitchSemiTones(self.handle, pitch)
    def setChannels(self, channels):
        dll.soundtouch_setChannels.argtypes = (c_void_p, c_uint,)
        dll.soundtouch_setChannels(self.handle, channels)
    def setSampleRate(self, rate):
        dll.soundtouch_setSampleRate.argtypes = (c_void_p, c_uint,)
        dll.soundtouch_setSampleRate(self.handle, rate)
    def flush(self):
        dll.soundtouch_flush(self.handle)
    def putSamples(self, samples):
        n = len(samples)
        data = (c_float * n)(*samples)
        dll.soundtouch_putSamples(self.handle, data, n)
    def putSamplesI16(self, samples):
        n = len(samples)
        data = (c_short * n)(*samples)
        dll.soundtouch_putSamples_i16(self.handle, data, n)
    def clear(self):
        dll.soundtouch_clear(self.handle)
    def setSetting(self, settingId, value):
        return dll.soundtouch_setSetting(self.handle, settingId, value)
    def getSetting(self, settingId):
        return dll.soundtouch_getSetting(self.handle, settingId)
    def numUnprocessedSamples(self):
        return dll.soundtouch_numUnprocessedSamples(self.handle)
    def receiveSamples(self):
        n = 4096
        buf = (c_float * (n + 1))()
        res = dll.soundtouch_receiveSamples(self.handle, buf, n)
        return buf[:res]
    def receiveSamplesI16(self):
        n=4096
        buf = (c_short * (n + 1))()
        res = dll.soundtouch_receiveSamples_i16(self.handle, buf, n)
        return buf[:res]
    def numSamples(self):
        return dll.soundtouch_numSamples(self.handle)
    def isEmpty(self):
        return dll.soundtouch_isEmpty(self.handle)
if __name__ == '__main__':
    print(getVersionId())
    print(getVersionString())
    a=SoundTouch()
    a.setChannels(2)
    a.setSampleRate(16000)
    a.putSamples((0,1,2,))
    a.putSamplesI16((0,1,2,3,))
    a.setSetting(SETTING_USE_ANTI_ALIAS_FILTER, 0)
    a.flush()
    a.clear()
    print(a.receiveSamples())
    print(a.isEmpty())